package com.zmn.mcc.cas.server;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.zmn.base.common.data.common.dro.company.CompanyContactDRO;
import com.zmn.base.common.data.dubbo.interfaces.company.CompanyListRemoteService;
import com.zmn.common.constant.GlobalConsts;
import com.zmn.common.dto2.ResponseDTO;
import com.zmn.common.utils.date.DateUtil;
import com.zmn.framework.core.http.HttpClientsFactory;
import com.zmn.mcc.cache.StaffCache;
import com.zmn.mcc.model.entity.staff.McStaff;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * @author quanjic
 * @version v1.0
 * @since 2020/6/16 11:34
 **/
@Slf4j
public class WhitelistServiceImpl implements WhitelistService {

    private RedisTemplate redisTemplate;

    private StaffCache staffCache;

    private String whitelistUrl;

    /**
     * 特殊区域，无需检查
     */
    private Integer noCheckAreaId;

    /**
     * IP白名单过期时间；单位：秒
     */
    private Integer expiredSecond;

    /**
     * 内网网段
     */
    private List<String> intranetSegmentList;

    private CompanyListRemoteService companyService;


    private Cache<String, Map> cache;

    final String WHITELIST_KEY = "whitelist";

    final String WHITELIST_REDIS_KEY = "cas:ip_whitelist";

    private Callable callable = new Callable() {
        @Override
        public Object call() {
            Map<Object, Object> redisCache = redisTemplate.opsForHash().entries(WHITELIST_REDIS_KEY);
            if (MapUtils.isNotEmpty(redisCache)) {
                return redisCache;
            } else {
                Map result = null;
                try {
                    result = loading();
                    redisTemplate.opsForHash().putAll(WHITELIST_REDIS_KEY, result);
                    redisTemplate.expire(WHITELIST_REDIS_KEY, expiredSecond, TimeUnit.SECONDS);
                    return result;
                } catch (Exception e) {
                    log.error("#cas#login#whitelist loading fail, uri: {}", whitelistUrl, e);
                    return result != null ? result : Collections.EMPTY_MAP;
                }
            }
        }
    };


    public WhitelistServiceImpl(String whitelistUrl, Integer expiredSecond, Integer noCheckAreaId) {
        if (expiredSecond == null || expiredSecond <= 0) {
            throw new NullPointerException("invalid expiredSecond");
        }
        if (StringUtils.isEmpty(whitelistUrl)) {
            throw new NullPointerException("invalid whitelistUrl");
        }
        this.whitelistUrl = whitelistUrl;
        this.expiredSecond = expiredSecond;
        this.noCheckAreaId = noCheckAreaId;
        cache = CacheBuilder.newBuilder().expireAfterWrite(expiredSecond.longValue(), TimeUnit.SECONDS).build();
    }

    private Map getWhitelist() {
        try {
            return cache.get(WHITELIST_KEY, callable);
        } catch (Exception e) {
            log.error("#cas#login#whitelist loading fail, uri: {}", whitelistUrl, e);
            return Collections.EMPTY_MAP;
        }
    }

    @NacosValue(value = "${cas.server.whitelist.enabled:true}", autoRefreshed = true)
    private boolean enabled = true;

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public boolean inWhitelist(String loginIp, Integer loginCityId) {
        if (StringUtils.isEmpty(loginIp) || loginCityId == null) {
            return false;
        }
        Object areaId = getWhitelist().get(loginIp);
        if (areaId == null) {
            log.warn("#cas#login#whitelist illegal login, ip: {}, city:{}", loginIp, loginCityId);
            return false;
        }
        areaId = areaId.toString();
        if (noCheckAreaId != null && areaId.equals(noCheckAreaId.toString())) {
            log.info("#cas#login#whitelist whitelist city, ip: {}, city:{}", loginIp, loginCityId);
            return true;
        }
        if (areaId.equals(loginCityId.toString())) {
            return true;
        }
        return false;
    }


    private static final String OPEN_IP_LOGIN_TODAY = "mcc:open:ip:login:today:";

    /**
     * 如果指定员工登录名是临时白名单用户(不做白名单过滤)，返回<code>true</code>
     *
     * @param staffId   员工ID
     * @param staffName 员工登录名
     * @return 如果指定员工登录名是临时白名单用户，返回<code>true</code>
     */
    private boolean isTempWhitelist(Integer staffId, String staffName) {

        if (staffCache.getOpenDateExists(staffId)) {
            return true;
        }

        // 废弃代码  TODO
        Object o = redisTemplate.opsForValue().get(OPEN_IP_LOGIN_TODAY + staffName);
        if (Objects.isNull(o)) {
            return false;
        }
        HashMap<String, Long> hashMap = (HashMap<String, Long>) o;
        long parse = hashMap.get("startTime");
        Date now = DateUtil.getNow();
        return now.getTime() >= parse;
    }

    @Override
    public boolean isWhitelistUser(McStaff staff) {
        return (staff.getWhitelist() != null && staff.getWhitelist() == GlobalConsts.YES) || this.isTempWhitelist(staff.getStaffId(), staff.getStaffName());
    }

    @Override
    public boolean canLogin(McStaff user, String ip) {
        if (user == null) {
            return false;
        }
        if (this.isWhitelistUser(user)) {
            return true;
        }
        if (isTempWhitelist(user.getStaffId(), user.getStaffName())) {
            return true;
        }

        // 内网ip校验
        if (intranetValid(ip)) {
            return true;
        }

        Integer companyId = user.getCompanyId();
        if (companyId == null || companyId < 1) {
            return false;
        }
        Integer cityId = null;
        ResponseDTO<CompanyContactDRO> responseDTO = companyService.getCompanyContactDROById(companyId);
        if (responseDTO.isSuccess()) {
            cityId = responseDTO.getData().getCityId();
        }
        return inWhitelist(ip, cityId);
    }

    /**
     * 内网校验
     * @param ip
     * @return
     */
    private Boolean intranetValid(String ip) {

        if (CollectionUtils.isEmpty(intranetSegmentList)) {
            return false;
        }

        // 校验 ip是否在 内网网段
        for (String s : intranetSegmentList) {
            boolean inRange = isInIntranet(ip, s);
            if (inRange) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param ip
     * @param cidr
     * @return
     */
    public static boolean isInIntranet(String ip, String cidr) {
        String[] ips = ip.split("\\.");
        int ipAddr = (Integer.parseInt(ips[0]) << 24)
                | (Integer.parseInt(ips[1]) << 16)
                | (Integer.parseInt(ips[2]) << 8) | Integer.parseInt(ips[3]);
        int type = Integer.parseInt(cidr.replaceAll(".*/", ""));
        int mask = 0xFFFFFFFF << (32 - type);
        String cidrIp = cidr.replaceAll("/.*", "");
        String[] cidrIps = cidrIp.split("\\.");
        int cidrIpAddr = (Integer.parseInt(cidrIps[0]) << 24)
                | (Integer.parseInt(cidrIps[1]) << 16)
                | (Integer.parseInt(cidrIps[2]) << 8)
                | Integer.parseInt(cidrIps[3]);

        return (ipAddr & mask) == (cidrIpAddr & mask);
    }

    private Map<String, String> loading() throws Exception {

        String content = HttpClientsFactory.getHttpClients().get(whitelistUrl);
        if (StringUtils.isBlank(content)) {
            log.warn("#cas#login#whitelist content is empty, uri: {}", whitelistUrl);
            return Collections.EMPTY_MAP;
        }

        log.debug("#cas#login#whitelist loading, uri: {}, content: {}", whitelistUrl, content);

        Map<String, String> result = new HashMap<>();

        JSONObject jsonObject = JSON.parseObject(content);
        JSONArray data = jsonObject.getJSONArray("data");
        if (data != null && data.size() > 0) {
            for (int i = 0; i < data.size(); i++) {
                JSONObject v1 = data.getJSONObject(i);
                String id = v1.getString("id");
                String areaId1 = v1.getString("areaId");
                result.put(id, areaId1);
            }
        }

        return result;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void setIntranetSegmentList(List<String> intranetSegmentList) {
        this.intranetSegmentList = intranetSegmentList;
    }

    public void setCompanyService(CompanyListRemoteService companyService) {
        this.companyService = companyService;
    }

    public StaffCache getStaffCache() {
        return staffCache;
    }

    public void setStaffCache(StaffCache staffCache) {
        this.staffCache = staffCache;
    }
}
